用法
使用 vm.$mount() 可以手动地挂载一个未挂载的实例,比如:1
2
3
4
5
6
7
8
9
10
11
12
13var MyComponent = Vue.extend({
template: '<div>Hello!</div>'
})
// 创建并挂载到 #app (会替换 #app)
new MyComponent().$mount('#app')
// 如果传入了el参数,则会直接调用$mount,最终效果和上面一样
new MyComponent({ el: '#app' })
// 或者,在文档之外渲染并且随后挂载
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)
分析
1 | // platforms\web\entry-runtime-with-compiler.js |
- 首先,缓存原型上的 $mount 方法,具体会跟运行环境不同而不同,如:浏览器 和 weex。
- 重写原型上的$mount方法,如果有el参数,则会查找该DOM元素,且Vue不允许挂载到 html 或 body 上面
调用 compileToFunctions 方法将 template 转换为 render 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// compiler\index.js
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 生成AST树
const ast = parse(template.trim(), options)
// 优化AST
if (options.optimize !== false) {
optimize(ast, options)
}
// generate函数会返回render与staticRenderFns
const code = generate(ast, options)
// 最后将 ast、render、staticRenderFns全部返回
return {
// AST
ast,
/*
这个是render函数
如果有这样一段代码
var v = new Vue({
el:'.arrow', data:{a:1}, template: '<div>{{ a }}</div>'
})
console.log(v.$options.render)
会得到:
ƒ anonymous() { with(this){return _c('div',[_v(_s(a))])} }
_c其实就是createElement函数的内部用法
Vue最终编译template的结果和我们直接用createElement手写render函数没两样。
*/
render: code.render,
// staticRenderFns 存放纯静态 render函数(就是没有使用data中的数据),比如这一段:
// var v = new Vue({
// el: '.arrow', data: { a: 1 }, template: '<div>hello</div>'
// })
// console.log(v.$options.staticRenderFns) // 这里输出就会看到里面的东西
staticRenderFns: code.staticRenderFns
}
})将 compileToFunctions 方法返回的 render 与 staticRenderFns 挂到options上
- 调用之前缓存的 mount.call(this, el, hydrating) 方法
1
2
3
4
5
6
7
8
9
10// 之前缓存的mount方法
// platforms\web\runtime\index.js
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
我们看到缓存的$mount方法调用了 mountComponent 方法,它在 core\instance\lifecycle.js 里面1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
// el
vm.$el = el
// 一些警告提示语
if (!vm.$options.render) {
// code...
}
// 触发 beforeMount 钩子
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
// 非生产环境下的性能监控
// code...
} else {
updateComponent = () => {
// vm._render() 将 render函数转换为 vNode ,vm._update 以其为参数发布更新
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
// 我们在 watcher的 构造函数中将 vm._watcher 设置为 this(isRenderWatcher参数)
// watcher 初始化时可能会调用 $forceUpdate 方法(比如:内部子组件的 mounted 钩子)
// 这需要 vm._watcher 已经定义过,如下:
// Vue.prototype.$forceUpdate = function () {
// const vm: Component = this
// if (vm._watcher) {
// vm._watcher.update()
// }
// }
// 实例化一个 Watcher 通过它的回调函数 updateComponent 来调用 vm._update 更新视图
new Watcher(vm, updateComponent, noop, {
before () {
// Vue已挂载且没被销毁才会触发 beforeUpdate 钩子
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher参数 */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
// 修改 vm._isMounted 为 true ,手动触发 mounted 钩子
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
mountComponent 中最主要的是 调用 vm._render() 将 render函数 转换为 vNode ,再调用 vm._update 以生成的 vNode 为参数发布更新。
1
2
3updateComponent = () => {
vm._update(vm._render(), hydrating)
}这里实例化一个 Watcher 通过它的回调函数 updateComponent 来调用 vm._update 更新视图
1
2
3
4
5
6
7
8new Watcher(vm, updateComponent, noop, {
before () {
// Vue已挂载且没被销毁才会触发 beforeUpdate 钩子
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher参数 */)修改 vm._isMounted 为 true ,触发 mounted 钩子
总结
- 获得模版
template
。 - 调用 compileToFunctions 获得 template 对应的
render函数
。 - 调用 mountComponent ,其中的
vm._render()
将render函数
转化为vNode
。 vm._update()
以生成的vNode
为参数发布更新。